package com.dl.dlcommon.utils;

import lombok.extern.slf4j.Slf4j;

import java.util.function.Supplier;

/**
 * 计算方法执行时间
 *
 * @author DL
 * @create 2024-03-14 20:22
 */
@Slf4j
public class TimeLogger {

    // 5 秒的纳秒数
    public static final long DEFAULT_TIMEOUT = 5_000_000_000L;

    private TimeLogger() {
        throw new AssertionError("No " + this.getClass().getName() + " instances for you!");
    }

    /**
     * 计算方法执行时间
     * @param task 方法
     * @author DL
     * @date 2024/04/08
     */
    public static void measureTime(Runnable task) {
        long startTime = System.nanoTime();
        task.run();
        long endTime = System.nanoTime();
        long executionTime = endTime - startTime;
        log(executionTime);
    }


    /**
     * 执行目标方法，并在控制台打印执行时间，返回执行结果
     *
     * @param <T>  目标方法的返回类型
     * @param task 目标方法
     * @return 执行结果
     */
    public static <T> T measureTimeMillis(Supplier<T> task) {
        ResultWithTime<T> tResultWithTime = measureTimeAndResult(task);
        return tResultWithTime.getResult();
    }

    /**
     * 执行目标方法，并返回执行时间（毫秒）和执行结果
     *
     * @param <T>  目标方法的返回类型
     * @param task 目标方法
     * @return 执行结果和执行时间的键值对
     */
    public static <T> ResultWithTime<T> measureTimeAndResult(Supplier<T> task) {
        long startTime = System.nanoTime();
        T result = task.get();
        long endTime = System.nanoTime();
        long executionTime = endTime - startTime;
        log(executionTime);
        return new ResultWithTime<>(result, executionTime);
    }

    /**
     * 打印日志
     * @param executionTime 执行时间
     * @author DL
     * @date 2024/04/08
     */
    public static void log(long executionTime) {
        if (executionTime > DEFAULT_TIMEOUT) {
            // 执行时间过长，可以在这里添加相应的逻辑
            log.error("方法执行时间过长，请注意优化");
        }
        log.info("方法用时 {} ms", String.format("%.3f", executionTime / 1_000_000.0));
    }

    /**
     * 封装执行结果和执行时间的内部类
     *
     * @param <T> 目标方法的返回类型
     */
    public static class ResultWithTime<T> {
        private final T result;
        private final long executionTime;

        public ResultWithTime(T result, long executionTime) {
            this.result = result;
            this.executionTime = executionTime;
        }

        public T getResult() {
            return result;
        }

        public long getExecutionTime() {
            return executionTime;
        }
    }

}

